{
 "cells": [
  {
   "cell_type": "code",
   "id": "76ac0e42092c0d50",
   "metadata": {},
   "source": [
    "import unsloth\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import math\n",
    "from modelscope import AutoTokenizer, AutoModelForCausalLM, snapshot_download\n",
    "from unsloth import FastLanguageModel\n",
    "# 设置pip国内镜像源（推荐清华源）\n",
    "import os"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "4252d7a6ab0128a9",
   "metadata": {},
   "source": [
    "max_seq_length = 2048\n",
    "dtype = None\n",
    "load_in_4bit = True"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "f195cd2d4033b116",
   "metadata": {},
   "source": [
    "model_name = \"unsloth/Qwen3-8B\"\n",
    "model_dir = snapshot_download(model_name)"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "46416b76f189aebd",
   "metadata": {},
   "source": [
    "model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "    model_dir,\n",
    "    max_seq_length=max_seq_length,\n",
    "    dtype=dtype,\n",
    "    load_in_4bit=load_in_4bit\n",
    ")"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "f8a5f33231d3ac37",
   "metadata": {},
   "source": [
    "prompt_style = \"\"\"以下是描述任务的指令，以及提供进一步上下文的输入。\n",
    "请写出一个适当完成请求的回答。\n",
    "在回答之前，请仔细思考问题，并创建一个逻辑连贯的思考过程，以确保回答准确无误。\n",
    "\n",
    "### 指令：\n",
    "你是一位精通卜卦、星象和运势预测的算命大师。\n",
    "请回答以下算命问题。\n",
    "\n",
    "### 问题：\n",
    "{}\n",
    "\n",
    "### 回答：\n",
    "<think>{}\"\"\"\n",
    "# 定义提示风格的字符串模板，用于格式化问题\n",
    "\n",
    "question = \"1992年闰四月初九巳时生人，女，想了解健康运势\"\n",
    "# 定义具体的算命问题"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": "model",
   "id": "a3b392e20739cbb8",
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "d613f7181ad5621f",
   "metadata": {},
   "source": [
    "# FastLanguageModel.for_inference(model)\n",
    "# # 准备模型以进行推理\n",
    "#\n",
    "# inputs = tokenizer([prompt_style.format(question, \"\")], return_tensors=\"pt\").to(\"cuda\")\n",
    "# # 使用 tokenizer 对格式化后的问题进行编码，并移动到 GPU\n",
    "#\n",
    "# outputs = model.generate(\n",
    "#     input_ids=inputs.input_ids,\n",
    "#     attention_mask=inputs.attention_mask,\n",
    "#     max_new_tokens=1200,\n",
    "#     use_cache=True,\n",
    "# )\n",
    "# # 使用模型生成回答\n",
    "#\n",
    "# response = tokenizer.batch_decode(outputs)\n",
    "# # 解码模型生成的输出为可读文本\n",
    "#\n",
    "# print(response[0])"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "d40fefdeaf4282f5",
   "metadata": {},
   "source": [
    "# 定义一个用于格式化提示的多行字符串模板\n",
    "train_prompt_style = \"\"\"以下是描述任务的指令，以及提供进一步上下文的输入。\n",
    "请写出一个适当完成请求的回答。\n",
    "在回答之前，请仔细思考问题，并创建一个逻辑连贯的思考过程，以确保回答准确无误。\n",
    "\n",
    "### 指令：\n",
    "你是一位精通八字算命、 紫微斗数、 风水、易经卦象、塔罗牌占卜、星象、面相手相和运势预测等方面的算命大师。\n",
    "请回答以下算命问题。\n",
    "\n",
    "### 问题：\n",
    "{}\n",
    "\n",
    "### 回答：\n",
    "<思考>\n",
    "{}\n",
    "</思考>\n",
    "{}\"\"\""
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "ef4fb4c886605e8e",
   "metadata": {},
   "source": [
    "EOS_TOKEN = tokenizer.eos_token  # 必须添加结束标记"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "dd3ce6d57a9b5126",
   "metadata": {},
   "source": [
    "from modelscope.msdatasets import MsDataset\n",
    "dataset_name = 'AI-ModelScope/fortune-telling'\n",
    "dataset = MsDataset.load(dataset_name, trust_remote_code=True, subset_name=\"default\", split=\"train\")"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "b826760f72fd4b04",
   "metadata": {},
   "source": [
    "len(dataset)"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "f8cf71f1c0b6f0d0",
   "metadata": {},
   "source": [
    "# 定义一个函数，用于格式化数据集中的每条记录\n",
    "def formatting_prompts_func(examples):\n",
    "    # 从数据集中提取问题、复杂思考过程和回答\n",
    "    inputs = examples[\"Question\"]\n",
    "    cots = examples[\"Complex_CoT\"]\n",
    "    outputs = examples[\"Response\"]\n",
    "    texts = []  # 用于存储格式化后的文本\n",
    "    # 遍历每个问题、思考过程和回答，进行格式化\n",
    "    for input, cot, output in zip(inputs, cots, outputs):\n",
    "        # 使用字符串模板插入数据，并加上结束标记\n",
    "        text = train_prompt_style.format(input, cot, output) + EOS_TOKEN\n",
    "        texts.append(text)  # 将格式化后的文本添加到列表中\n",
    "    return {\n",
    "        \"text\": texts,  # 返回包含所有格式化文本的字典\n",
    "    }\n",
    "\n",
    "dataset = dataset.map(formatting_prompts_func, batched = True)\n",
    "dataset[\"text\"][0]"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "93e79c10358fbd98",
   "metadata": {},
   "source": [
    "FastLanguageModel.for_training(model)\n",
    "\n",
    "model = FastLanguageModel.get_peft_model(\n",
    "    model,  # 传入已经加载好的预训练模型\n",
    "    r = 16,  # 设置 LoRA 的秩，决定添加的可训练参数数量\n",
    "    target_modules = [\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",  # 指定模型中需要微调的关键模块\n",
    "                      \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
    "    lora_alpha = 16,  # 设置 LoRA 的超参数，影响可训练参数的训练方式\n",
    "    lora_dropout = 0,  # 设置防止过拟合的参数，这里设置为 0 表示不丢弃任何参数\n",
    "    bias = \"none\",    # 设置是否添加偏置项，这里设置为 \"none\" 表示不添加\n",
    "    use_gradient_checkpointing = \"unsloth\",  # 使用优化技术节省显存并支持更大的批量大小\n",
    "    random_state = 3407,  # 设置随机种子，确保每次运行代码时模型的初始化方式相同\n",
    "    use_rslora = False,  # 设置是否使用 Rank Stabilized LoRA 技术，这里设置为 False 表示不使用\n",
    "    loftq_config = None,  # 设置是否使用 LoftQ 技术，这里设置为 None 表示不使用\n",
    ")"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "7b5f57cfda77e0b2",
   "metadata": {},
   "source": [
    "from trl import SFTTrainer, SFTConfig  # 导入 SFTTrainer，用于监督式微调\n",
    "from transformers import TrainingArguments  # 导入 TrainingArguments，用于设置训练参数\n",
    "from unsloth import is_bfloat16_supported  # 导入函数，检查是否支持 bfloat16 数据格式\n",
    "\n",
    "SFTConfig()\n",
    "\n",
    "trainer = SFTTrainer(  # 创建一个 SFTTrainer 实例\n",
    "    model=model,  # 传入要微调的模型\n",
    "    tokenizer=tokenizer,  # 传入 tokenizer，用于处理文本数据\n",
    "    train_dataset=dataset,  # 传入训练数据集\n",
    "    dataset_text_field=\"text\",  # 指定数据集中文本字段的名称\n",
    "    max_seq_length=max_seq_length,  # 设置最大序列长度\n",
    "    dataset_num_proc=1,  # 设置数据处理的并行进程数\n",
    "    packing=False,  # 是否启用打包功能（这里设置为 False，打包可以让训练更快，但可能影响效果）\n",
    "    args=TrainingArguments(  # 定义训练参数\n",
    "        per_device_train_batch_size=1,  # 每个设备（如 GPU）上的批量大小\n",
    "        gradient_accumulation_steps=4,  # 梯度累积步数，用于模拟大批次训练\n",
    "        warmup_steps=5,  # 预热步数，训练开始时学习率逐渐增加的步数\n",
    "        # max_steps=75,  # 最大训练步数\n",
    "        num_train_epochs=5,\n",
    "        learning_rate=2e-4,  # 学习率，模型学习新知识的速度\n",
    "        fp16=not is_bfloat16_supported(),  # 是否使用 fp16 格式加速训练（如果环境不支持 bfloat16）\n",
    "        bf16=is_bfloat16_supported(),  # 是否使用 bfloat16 格式加速训练（如果环境支持）\n",
    "        logging_steps=1,  # 每隔多少步记录一次训练日志\n",
    "        optim=\"adamw_8bit\",  # 使用的优化器，用于调整模型参数\n",
    "        weight_decay=0.01,  # 权重衰减，防止模型过拟合\n",
    "        lr_scheduler_type=\"linear\",  # 学习率调度器类型，控制学习率的变化方式\n",
    "        seed=3407,  # 随机种子，确保训练结果可复现\n",
    "        output_dir=\"outputs\",  # 训练结果保存的目录\n",
    "        report_to=\"none\",  # 是否将训练结果报告到外部工具（如 WandB），这里设置为不报告\n",
    "    ),\n",
    ")"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "2e3ceeb63033a1ff",
   "metadata": {},
   "source": [
    "trainer_stats = trainer.train()"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "c5596494ea0593e0",
   "metadata": {},
   "source": [
    "print(question) # 打印前面的问题"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "55c3af52d4340944",
   "metadata": {},
   "source": [
    "# 将模型切换到推理模式，准备回答问题\n",
    "FastLanguageModel.for_inference(model)\n",
    "\n",
    "# 将问题转换成模型能理解的格式，并发送到 GPU 上\n",
    "inputs = tokenizer([prompt_style.format(question, \"\")], return_tensors=\"pt\").to(\"cuda\")\n",
    "\n",
    "# 让模型根据问题生成回答，最多生成 4000 个新词\n",
    "outputs = model.generate(\n",
    "    input_ids=inputs.input_ids,  # 输入的数字序列\n",
    "    attention_mask=inputs.attention_mask,  # 注意力遮罩，帮助模型理解哪些部分重要\n",
    "    max_new_tokens=40000,  # 最多生成 4000 个新词\n",
    "    use_cache=True,  # 使用缓存加速生成\n",
    ")\n",
    "\n",
    "# 将生成的回答从数字转换回文字\n",
    "response = tokenizer.batch_decode(outputs)\n",
    "\n",
    "# 打印回答\n",
    "print(response[0])"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "e298fad6b22303c4",
   "metadata": {},
   "source": [
    "model.save_pretrained_gguf(\"model\", tokenizer,)"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "e3e3c092a02df9cc",
   "metadata": {},
   "source": [
    "model.save_pretrained_gguf(\"model\", tokenizer, quantization_method=\"q4_k_m\")\n"
   ],
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
